#include <arpa/inet.h>
#include <netinet/in.h>
#include <time.h>

#define MAX_PACKET_SIZE 1500

// PRIORITY MACROS
#define HIGH 0x01
#define MED  0x02
#define LOW  0x03

// ERROR MESSAGE MACROS
#define ERROR_SENDER_USAGE "error_usage: sender -p <port> -g <requester port> -r <rate> -q <seq_no> -l <length> -f <f_hostname> -h <f_port> -i <priority> -t <timeout>"
#define ERROR_REQUESTER_USAGE "error_usage: requester -p <port> -f <f_hostname> -h <f_port> -o <file option> -w <window>"
#define ERROR_EMULATOR_USAGE "error_usage: emulator -p <port> -q <queue_size> -f <filename> -l <log>"
#define ERROR_RT_USAGE "error_usage: trace -a <routetrace port> -b < source hostname> -c <source port> -d <destination hostname> -e <destination port> -f <debug option>"
#define ERROR_SOCKET "error_socket: socket creation failure"
#define ERROR_PORT_RANGE "error_port: port number out of range"
#define ERROR_BIND "error_bind: bind failure"
#define ERROR_SENDTO "error_sendto: sendto returned -1"
#define ERROR_RECVFROM "error_recvfrom: recvfrom returned -1"
#define ERROR_FILEREAD "error_fileread: tracker.txt not found"
#define ERROR_PRIORITY "error_priority: unknown priority level"
#define ERROR_SEND_NUM "error_send_num: packet has been been sent too many times"

// LOGGING MACROS
#define LOG_NO_ENTRY    1
#define LOG_FULL_QUEUE1 2
#define LOG_FULL_QUEUE2 3
#define LOG_FULL_QUEUE3 4
#define LOG_LOSS_EVENT  5

// PACKET STRUCTURE
typedef struct Packet
{
  char type;
  uint32_t sequence;
  uint32_t length;
  char payload[];
} __attribute__((packed)) Packet;

// EMBEDDED PACKET STRUCTURE
typedef struct Em_Packet
{
  char priority;
  struct in_addr src_IP;
  uint16_t src_port;
  struct in_addr dest_IP;
  uint16_t dest_port;
  uint32_t length;
  char payload[];
} __attribute__((packed)) Em_Packet;

// TRACEROUTE PACKET STRUCTURE
typedef struct RT_Packet
{
  char type;
  uint32_t ttl;
  struct in_addr src_IP;
  uint16_t src_port;
  struct in_addr dest_IP;
  uint16_t dest_port;
}__attribute__((packed)) RT_Packet;

//WINDOW STRUCTURE
typedef struct Window_Element
{
  char em_packet[1500];
  int sent;
  int acked;
  long to; // the time in milliseconds that the element was first sent
  int times_sent;
} Window_Element;

//load packet
void load_packet(Packet * packet, char pkt_type,
			int pkt_seq_num, int pkt_payload_length, 
			char* pkt_payload)
{
  //    printf("payload: %s\n", pkt_payload);
    packet->type = pkt_type;
    packet->sequence = pkt_seq_num;
    packet->length = pkt_payload_length;

    memcpy(packet->payload, pkt_payload, MAX_PACKET_SIZE - 
           sizeof(Packet));
    //    printf("packet error: %s\n", packet->payload);
}

//load em_packet
void load_em_packet(Em_Packet * em_packet, char priority,
			char* src_IP, uint16_t src_port,
			char* dest_IP, uint16_t dest_port,
			uint32_t length , Packet* packet)
{
    em_packet->priority = priority;

    // Set up the src
    struct hostent *tmp_host = gethostbyname(src_IP);
    memcpy(&(em_packet->src_IP), (char*)tmp_host->h_addr, sizeof(struct in_addr));
    em_packet->src_port = src_port;

    // Set up the dest
    tmp_host = gethostbyname(dest_IP);
    memcpy(&(em_packet->dest_IP), (char*)tmp_host->h_addr, sizeof(struct in_addr));
    em_packet->dest_port = dest_port;

    em_packet->length = length;

    // not sure how to get size of payload
    memcpy(em_packet->payload, (char*)packet, length); 

    em_packet->payload[length] = '\0';
}

void load_rt_packet(RT_Packet * rt_packet, char pkt_type, uint32_t time_to_live,
		char* src_IP, uint16_t src_port, char* dest_IP,
		uint16_t dest_port)
{
    rt_packet->type = pkt_type;
    rt_packet->ttl = time_to_live;

    struct hostent *tmp_host = gethostbyname(src_IP);
    memcpy(&(rt_packet->src_IP), (char*)tmp_host->h_addr, sizeof(struct in_addr));
    rt_packet->src_port = src_port;

    tmp_host = gethostbyname(dest_IP);
    memcpy(&(rt_packet->dest_IP), (char*)tmp_host->h_addr, sizeof(struct in_addr));
    rt_packet->dest_port = dest_port;
}

// GLOBAL STRUCTURES
Packet* packet;
Em_Packet* em_packet;
RT_Packet* rt_packet;
char packet_buf[MAX_PACKET_SIZE + 1]; // To allow a '\0'
                                      // to be added to the end
char em_packet_buf[MAX_PACKET_SIZE + 1]; // To allow a '\0'
                                      // to be added to the end
char rt_packet_buf[MAX_PACKET_SIZE + 1]; // To allow a '\0'
				      // to be added to the end
char heartbeat_packet_buf[MAX_PACKET_SIZE + 1]; // To allow a '\0'
                                      // to be added to the end

// USEFUL METHODS
void file_print(char *logname, Em_Packet *em_packet, int log_code)
{
    int size = 30;
    char str_time[size];
    struct timeval time;
    struct tm* full_time;
    char* log_msg;
    FILE *logfile;
    Packet *packet = (Packet*)em_packet->payload;

    logfile = fopen(logname, "a");

    gettimeofday(&time, NULL);
    //clock_gett(time(CLOCK_REALTIME, &time);
    full_time = localtime(&time.tv_sec);

    strftime(str_time, size, "[%m:%d]%I:%M:%S.", full_time);

    switch(log_code)
    {
        case LOG_NO_ENTRY:
           log_msg = "no forwarding entry found";
           break;
        case LOG_FULL_QUEUE1:
           log_msg = "priority queue 1 was full";
           break;
        case LOG_FULL_QUEUE2:
           log_msg = "priority queue 2 was full";
           break;
        case LOG_FULL_QUEUE3:
           log_msg = "priority queue 3 was full";
           break;
        case LOG_LOSS_EVENT:
           log_msg = "loss event occurred";
           break;
        default:
           log_msg = "unknown error";
    }

    struct hostent* tmp = gethostbyaddr(&em_packet->src_IP, sizeof(struct in_addr), AF_INET);


    //Packet *packet = (Packet*)em_packet->payload;
    fprintf(logfile, "%s%d\t(prior = %d, len = %d)\n\ttype: %c\t\tseq no: %d\n\tERROR: %s\n\tsrc: [%s, %d]\n\t",  str_time, (int)time.tv_usec, em_packet->priority, em_packet->length, packet->type, packet->sequence, log_msg, tmp->h_name, em_packet->src_port);

    tmp = gethostbyaddr(&em_packet->dest_IP, sizeof(struct in_addr), AF_INET);
    fprintf(logfile, "dest: [%s, %d]\n", tmp->h_name, em_packet->dest_port);
    fclose(logfile);

}

void print_em_packet(Em_Packet *em_packet)
{
    // Requires a stupid broken print statement due to inet_ntoa()
    printf("\t\t\033[34mEM_HEADER: (prior = %d, len = %d)\033[0m\n\033[1;34msrc: [%s, %d]\t", em_packet->priority, em_packet->length, inet_ntoa(em_packet->src_IP), em_packet->src_port);

    printf("dest: [%s, %d]\033[0m\n", inet_ntoa(em_packet->dest_IP), em_packet->dest_port);
}

void print_Packet(Packet *packet)
{
    printf("\033[35mPKT #%u: [%c, %u]\033[0m\n\n", packet->sequence, packet->type, packet->length);
}

void print_sent_packet(Packet *packet, struct sockaddr_in *addr, int sent_bytes)
{
    printf("\033[32mPKT_SENT to %s:\tPKT #%u of %d bytes: [%c, %u]\033[1;32m\n< PAYLOAD >\n%.4s\033[0m\n\n", inet_ntoa(*((struct in_addr*)&(addr->sin_addr.s_addr))), packet->sequence, sent_bytes, packet->type, packet->length, packet->payload);
}

void print_recv_packet(Packet *packet, struct sockaddr_in *addr, int sent_bytes, int tot_recv_bytes)
{
    printf("\033[31mPKT_RECV from %s:\tPKT #%u of %d/%d bytes: [%c, %u]\033[1;31m\n< PAYLOAD >\n%.4s\033[0m\n\n", inet_ntoa(*((struct in_addr*)&(addr->sin_addr.s_addr))), packet->sequence, sent_bytes, tot_recv_bytes, packet->type, packet->length, packet->payload);
}
